use super::vm::VariableValue;
use std::collections::HashMap;

pub fn get_builtins() -> HashMap<String, VariableValue> {
    let mut builtins = HashMap::new();

    builtins.insert("print".to_string(), VariableValue::Function(print));
    builtins.insert("printVM".to_string(), VariableValue::Function(print_vm));
    builtins.insert("sleep".to_string(), VariableValue::Function(sleep));
    builtins.insert("random".to_string(), VariableValue::Function(vm_random));
    builtins.insert("floor".to_string(), VariableValue::Function(floor));
    builtins.insert("round".to_string(), VariableValue::Function(round));
    builtins.insert("abs".to_string(), VariableValue::Function(abs));
    builtins.insert("sqrt".to_string(), VariableValue::Function(sqrt));
    builtins.insert("invert".to_string(), VariableValue::Function(invert));
    builtins.insert("length".to_string(), VariableValue::Function(length));

    // file
    builtins.insert(
        "readFileToString".to_string(),
        VariableValue::Function(read_file_to_string),
    );
    builtins.insert(
        "writeStringToFile".to_string(),
        VariableValue::Function(write_string_to_file),
    );

    // web http
    builtins.insert(
        "webRequest".to_string(),
        VariableValue::Function(web_request),
    );

    // string
    builtins.insert(
        "replace".to_string(),
        VariableValue::Function(string_replace),
    );
    builtins.insert("split".to_string(), VariableValue::Function(string_split));
    builtins.insert("concat".to_string(), VariableValue::Function(string_concat));
    builtins.insert("find".to_string(), VariableValue::Function(string_find));
    builtins.insert("match".to_string(), VariableValue::Function(string_match));

    return builtins;
}

pub fn print(args: Vec<String>) -> VariableValue {
    if args.len() == 0 {
        return VariableValue::Err("print arg not found\n".to_string());
    }
    let s = args.join(",");
    println!("{}", s);
    VariableValue::Null
}

pub fn print_vm(args: Vec<String>) -> VariableValue {
    if args.len() == 0 {
        return VariableValue::Err("print arg not found\n".to_string());
    }
    let s = args.join(",");
    println!("{} %#%# ", s);
    VariableValue::Null
}

pub fn sleep(args: Vec<String>) -> VariableValue {
    if args.len() == 0 {
        return VariableValue::Err("sleep arg not found".to_string());
    }
    if let Ok(t) = args[0].parse::<u64>() {
        std::thread::sleep(std::time::Duration::from_millis(t));
        VariableValue::Null
    } else {
        VariableValue::Err("sleep arg not number".to_string())
    }
}

pub fn vm_random(_args: Vec<String>) -> VariableValue {
    let a = rand::random::<f64>();
    return VariableValue::Number(a);
}

pub fn floor(args: Vec<String>) -> VariableValue {
    if args.len() == 0 {
        return VariableValue::Err("floor arg not found".to_string());
    }
    let res = args[0].parse::<f64>();

    if let Ok(num) = res {
        VariableValue::Number(num.floor())
    } else {
        VariableValue::Err("floor arg not number".to_string())
    }
}

pub fn round(args: Vec<String>) -> VariableValue {
    if args.len() == 0 {
        return VariableValue::Err("round arg not found".to_string());
    }
    let res = args[0].parse::<f64>();

    if let Ok(num) = res {
        VariableValue::Number(num.round())
    } else {
        VariableValue::Err("round arg not number".to_string())
    }
}

pub fn ceil(args: Vec<String>) -> VariableValue {
    if args.len() == 0 {
        return VariableValue::Err("ceil arg not found".to_string());
    }
    let res = args[0].parse::<f64>();

    if let Ok(num) = res {
        VariableValue::Number(num.ceil())
    } else {
        VariableValue::Err("ceil arg not number".to_string())
    }
}

pub fn abs(args: Vec<String>) -> VariableValue {
    if args.len() == 0 {
        return VariableValue::Err("abs arg not found".to_string());
    }
    let res = args[0].parse::<f64>();

    if let Ok(num) = res {
        VariableValue::Number(num.abs())
    } else {
        VariableValue::Err("abs arg not number".to_string())
    }
}

pub fn sqrt(args: Vec<String>) -> VariableValue {
    if args.len() == 0 {
        return VariableValue::Err("sqrt arg not found".to_string());
    }
    let res = args[0].parse::<f64>();

    if let Ok(num) = res {
        VariableValue::Number(num.sqrt())
    } else {
        VariableValue::Err("sqrt arg not number".to_string())
    }
}

pub fn invert(args: Vec<String>) -> VariableValue {
    if args.len() == 0 {
        return VariableValue::Err("invert arg not found".to_string());
    }
    let res = args[0].parse::<bool>();

    if let Ok(num) = res {
        VariableValue::Boolean(!num)
    } else {
        VariableValue::Err("invert arg not bool".to_string())
    }
}

pub fn length(args: Vec<String>) -> VariableValue {
    if args.len() == 0 {
        return VariableValue::Err("invert arg not found".to_string());
    }
    VariableValue::Number(args[0].len() as f64)
}

pub fn read_file_to_string(args: Vec<String>) -> VariableValue {
    if args.len() < 2 {
        return VariableValue::Err("readFileToString arg need 2".to_string());
    }
    let return_type = &args[0];
    let path = std::path::Path::new(&args[1]);

    match std::fs::metadata(path) {
        Ok(meta) => {
            if !meta.is_file() {
                return VariableValue::Err("path not file".to_string());
            } else if meta.len() > 10 * 1024 * 1024 {
                return VariableValue::Err("file too large,limit 10MB".to_string());
            }
            if return_type == "string" {
                let content = std::fs::read_to_string(path);
                match content {
                    Ok(s) => VariableValue::String(s),
                    Err(_) => VariableValue::Err("file read error".to_string()),
                }
            } else {
                let content = std::fs::read(path);
                match content {
                    Ok(v) => VariableValue::Binary(v),
                    Err(_) => VariableValue::Err("file read error".to_string()),
                }
            }
        }
        Err(e) => VariableValue::Err(format!("{} not exist,e:{}", path.to_string_lossy(), e)),
    }
}

pub fn write_string_to_file(args: Vec<String>) -> VariableValue {
    if args.len() < 2 {
        return VariableValue::Err("writeFileToString args need 2".to_string());
    }
    let return_type = &args[0];
    let path = std::path::Path::new(&args[1]);
    let content = &args[2];
    if return_type == "string" {
        let res = std::fs::write(path, content);
        if let Err(_) = res {
            return VariableValue::Err("file write error".to_string());
        }
    } else {
        let temp = content
            .split(",")
            .map(|s| s.parse::<u8>().unwrap())
            .collect::<Vec<_>>();
        let res = std::fs::write(path, temp);
        if let Err(_) = res {
            return VariableValue::Err("file write error".to_string());
        }
    }

    println!("{} write success", path.to_string_lossy());
    VariableValue::Null
}

pub fn web_request(args: Vec<String>) -> VariableValue {
    if args.len() < 6 {
        return VariableValue::Err("WebRequest args need 4".to_string());
    }

    let method = &args[0];
    let headers = args[1].split("&").collect::<Vec<_>>();
    let body = &args[2];
    let time_out: u64;
    let res_type = &args[4];
    let url = &args[5];
    if let Ok(t) = &args[3].parse::<u64>() {
        time_out = *t;
    } else {
        return VariableValue::Err("WebRequest time_out not number".to_string());
    }
    match super::utils::web_http_request(url, method, headers, body, time_out) {
        Ok(res) => {
            if res_type == "string" {
                match String::from_utf8(res) {
                    Ok(s) => VariableValue::String(s),
                    Err(_) => VariableValue::Err("WebRequest response not string".to_string()),
                }
            } else {
                VariableValue::Binary(res)
            }
        }
        Err(e) => VariableValue::Err(e),
    }
}

pub fn string_replace(args: Vec<String>) -> VariableValue {
    if args.len() < 4 {
        return VariableValue::Err("stringReplace args need 4".to_string());
    }
    let search_str = &args[1];
    let origin_str = &args[2];
    let new_str = &args[3];

    if let Ok(reg) = regex::Regex::new(search_str) {
        let new_var = reg.replace_all(origin_str, new_str);
        VariableValue::String(new_var.to_string())
    } else {
        return VariableValue::Err("searchString not regex".to_string());
    }
}

pub fn string_split(args: Vec<String>) -> VariableValue {
    if args.len() < 3 {
        return VariableValue::Err("stringSplit args need 3".to_string());
    }
    let search_str = &args[1];
    let origin_str = &args[2];
    if let Ok(reg) = regex::Regex::new(search_str) {
        let mut res = Vec::<String>::new();
        let caps = reg.split(origin_str).collect::<Vec<_>>();
        for cap in caps {
            res.push(cap.to_string());
        }
        VariableValue::Array(res)
    } else {
        return VariableValue::Err("searchString not regex".to_string());
    }
}

pub fn string_concat(args: Vec<String>) -> VariableValue {
    if args.len() < 4 {
        return VariableValue::Err("stringConcat args need 4".to_string());
    }
    let origin_str = &args[2];
    let new_str = &args[3];

    let res = format!("{}{}", origin_str, new_str);
    VariableValue::String(res)
}

pub fn string_find(args: Vec<String>) -> VariableValue {
    if args.len() < 3 {
        return VariableValue::Err("stringFind args need 3".to_string());
    }

    let search_str = &args[1];
    let origin_str = &args[2];

    if let Ok(reg) = regex::Regex::new(search_str) {
        let mut res = Vec::<String>::new();
        if let Some(caps) = reg.captures(origin_str) {
            for cap in caps.iter() {
                if let Some(m) = cap {
                    res.push(m.as_str().to_string())
                }
            }
        }
        VariableValue::Array(res)
    } else {
        return VariableValue::Err("searchString not regex".to_string());
    }
}

pub fn string_match(args: Vec<String>) -> VariableValue {
    if args.len() < 3 {
        return VariableValue::Err("stringMatch args need 3".to_string());
    }

    let search_str = &args[1];
    let origin_str = &args[2];

    if let Ok(res) = regex::Regex::new(search_str) {
        VariableValue::Boolean(res.is_match(origin_str))
    } else {
        VariableValue::Boolean(false)
    }
}
